#include "transition.h"
#include "game_flow_manager.h"
#include "game_state.h"

#include "audio/audio.h"
#include "console/console.h"
#include "graphics/graphics.h"
#include "graphics/graphics_util.h"
#include "util/timer.h"				// log
#include "util/log.h"				// log
#include "util/common_macros.h"		// macros
#include "util/mmgr.h"		    

#include <math.h>
#include <stdlib.h> 


using namespace gameflow;


class GameTransitionFade : public GameTransition
{
public:
	static GameTransition* Create(const std::string& Name)	{	return new GameTransitionFade(Name);	}
	GameTransitionFade(const std::string& Name) :	GameTransition(Name)
	{
	}
	virtual void	Render()
	{
		Graphics::Vertex2D*	VertA = Graphics::GetTempVert();
		int		Alpha = int(GetProg()*255);
		Graphics* pG = GetGraphics();
		COLOR	Col = ColorRGBA(m_R, m_G, m_B, Alpha);
		math::Vec2	UV1 = (m_pOwner) ? m_pOwner->GetUV1() : math::Vec2(0,0);
		math::Vec2	UV2 = (m_pOwner) ? m_pOwner->GetUV2() : math::Vec2(1,1);
		if (!m_pOwner) pG->SetTexture(0, NULL); else m_pOwner->SetTexture();
		GraphicsUtil::FillQuad(math::Vec2(0,0), math::Vec2(1,1), UV1, UV2, Col, VertA);
		pG->DrawVertex2D(VertA, 6);
	}
};

class GameTransitionFadeWhite : public GameTransition
{
public:
	static GameTransition* Create(const std::string& Name)	{	return new GameTransitionFadeWhite(Name);	}
	GameTransitionFadeWhite(const std::string& Name) :	GameTransition(Name)
	{
		m_Timer.SetLimit(0.3f);
	}
	virtual void	Render()
	{
		Graphics::Vertex2D*	VertA = Graphics::GetTempVert();
		math::Vec2	UV1 = (m_pOwner) ? m_pOwner->GetUV1() : math::Vec2(0,0);
		math::Vec2	UV2 = (m_pOwner) ? m_pOwner->GetUV2() : math::Vec2(1,1);
		int		Alpha = int(GetProg()*255);
		COLOR	Col = ColorRGBA(m_R, m_G, m_B, Alpha);
		Graphics* pG = GetGraphics();
		if (!m_pOwner) pG->SetTexture(0, NULL);else m_pOwner->SetTexture();

		GraphicsUtil::FillQuad(math::Vec2(0,0), math::Vec2(1,1), UV1, UV2, Col, VertA);
		pG->DrawVertex2D(VertA, 6);
	}
};


class GameTransitionIris : public GameTransition
{
public:
	static GameTransition* Create(const std::string& Name)	{	return new GameTransitionIris(Name);	}
	GameTransitionIris(const std::string& Name) :	GameTransition(Name)
	{
	}
	math::Vec2		GetPos(float	Angle, float Rad)
	{
		math::Vec2	P;
		P.x = 0.5f + (cosf(Angle) * Rad*0.5f);
		P.y = 0.5f + (sinf(Angle) * Rad*0.5f);
		return P;
	}
	virtual void	Render()
	{
		Graphics::Vertex2D*	VertA = Graphics::GetTempVert();
		float	Prog = GetProg();
		COLOR	Col = ColorRGBA(m_R, m_G, m_B, 255);
		// basically we want to create an expanding/contracting iris
		math::Vec2	UV1 = (m_pOwner) ? m_pOwner->GetUV1() : math::Vec2(0,0);
		math::Vec2	UV2 = (m_pOwner) ? m_pOwner->GetUV2() : math::Vec2(1,1);
		math::Vec2	UVD = UV2-UV1;

		float			FromRad = 1.7f * (1.0f-Prog);		// radius from
		float			ToRad = 1.7f;		// radius to
		const int		SLICE_C	= 32;	// number of slices
		float			Slice2Rad = (2 * 3.14f) / float(SLICE_C-1);
		int				Index = 0;
		COLOR			ColorA[4] = {	Col, Col, Col, Col };
		math::Vec2		UVA[4]	= {	UV1, math::Vec2(UV2.x,UV1.y), UV2, math::Vec2(UV1.x,UV2.y) };
		math::Vec2		PosA[4];

		for (int i=0; i<SLICE_C; i++)
		{
			PosA[0] = GetPos((i+1)*Slice2Rad, ToRad);
			PosA[1] = GetPos((i+0)*Slice2Rad, ToRad);
			PosA[2] = GetPos((i+1)*Slice2Rad, FromRad);
			PosA[3] = GetPos((i+0)*Slice2Rad, FromRad);
			for (int j=0; j<4; j++)
			{
				UVA[j] = UV1 + math::Vec2(UVD.x * PosA[j].x,  UVD.y * PosA[j].y);
			}

			GraphicsUtil::FillQuad(PosA, UVA, ColorA, &VertA[Index]);
			Index+=6;
		}

		Graphics* pG = GetGraphics();
		if (!m_pOwner) pG->SetTexture(0, NULL);else m_pOwner->SetTexture();

		pG->DrawVertex2D(&VertA[0], Index);
	}
};

class GameTransitionRoll : public GameTransition
{
public:
	static GameTransition* Create(const std::string& Name)	{	return new GameTransitionRoll(Name);	}
	GameTransitionRoll(const std::string& Name) :	GameTransition(Name)
	{
	}
	virtual void	Render()
	{
		Graphics::Vertex2D*	VertA = Graphics::GetTempVert();
		math::Vec2	UV1 = (m_pOwner) ? m_pOwner->GetUV1() : math::Vec2(0,0);
		math::Vec2	UV2 = (m_pOwner) ? m_pOwner->GetUV2() : math::Vec2(1,1);

		float	Prog = m_Timer.GetProgress();
		COLOR	c = ColorRGBA(m_R, m_G, m_B, 255);
		if (m_Reverse)
			GraphicsUtil::FillQuad(math::Vec2(Prog, 0), math::Vec2(1, 1), UV1, UV1 + ((UV2-UV1)*Prog), c, VertA);
		else
			GraphicsUtil::FillQuad(math::Vec2(0, 0), math::Vec2(Prog, 1), UV1, UV1 + ((UV2-UV1)*Prog), c, VertA);
		int Index = 6;

		Graphics* pG = GetGraphics();
		if (!m_pOwner) pG->SetTexture(0, NULL);else m_pOwner->SetTexture();

		pG->DrawVertex2D(&VertA[0], Index);

	}
};

class GameTransitionGrid : public GameTransition
{
public:
	static GameTransition* Create(const std::string& Name)	{	return new GameTransitionGrid(Name);	}
	GameTransitionGrid(const std::string& Name) :	GameTransition(Name)
	{
	}
	static const int	GRID_X = 60;
	static const int	GRID_Y = 40;
	virtual bool	IsGridOpen(int x, int y)
	{
		// 0,0 is open at 0
		// N,N is open at 1
		int		Index = x+y;
		float	Prog = m_Timer.GetProgress();
		int		Current = (int)(Prog * (GRID_X + GRID_Y));
		if (m_Reverse)
		{
			return Index >=Current;
		} else
		{
			return Index <= Current;
		}
	}
	virtual void	Render()
	{
		int		Index = 0;
		COLOR	c = ColorRGBA(m_R, m_G, m_B, 255);
		math::Vec2	Size(1.0f/float(GRID_X), 1.0f/float(GRID_Y));
		math::Vec2	UV1 = (m_pOwner) ? m_pOwner->GetUV1() : math::Vec2(0,0);
		math::Vec2	UV2 = (m_pOwner) ? m_pOwner->GetUV2() : math::Vec2(1,1);
		math::Vec2	DUV = UV2-UV1;

		Graphics* pG = GetGraphics();
		Graphics::Vertex2D*	VertA = Graphics::GetTempVert();

		for (unsigned int x=0; x<GRID_X; x++)
		for (unsigned int y=0; y<GRID_Y; y++)
		{
			if (IsGridOpen(x, y))
			{
				math::Vec2	P(Size.x*x, Size.y*y);
				math::Vec2	TUV1 = UV1 + math::Vec2(DUV.x * (x+0) / float(GRID_X), DUV.y * (y+0) / float(GRID_Y)); 
				math::Vec2	TUV2 = UV1 + math::Vec2(DUV.x * (x+1) / float(GRID_X), DUV.y * (y+1) / float(GRID_Y)); 
				GraphicsUtil::FillQuad(P, Size + math::Vec2(GraphicsUtil::InvW, GraphicsUtil::InvH), TUV1, TUV2, c, &VertA[Index]);
				Index+=6;
				if (Index >= Graphics::MAX_TEMP_VERT - 6)
				{
					pG->DrawVertex2D(&VertA[0], Index);
					Index = 0;
				}
			}
		}
		if (!m_pOwner) pG->SetTexture(0, NULL); else m_pOwner->SetTexture();

		if (Index>3)
			pG->DrawVertex2D(&VertA[0], Index);
	}
};

class GameTransitionCircle : public GameTransitionGrid
{
public:
	static GameTransition* Create(const std::string& Name)	{	return new GameTransitionCircle(Name);	}
	GameTransitionCircle(const std::string& Name) :	GameTransitionGrid(Name)
	{
	}

	virtual bool	IsGridOpen(int x, int y)
	{
		float	dx = fabs(float((GRID_X/2) - x));
		float	dy = fabs(float((GRID_Y/2) - y));
		float	mx = float(GRID_X/2);
		float	my = float(GRID_Y/2);
		float	Index = sqrtf( (dx*dx) + (dy*dy) ) ;
		float	Prog = m_Timer.GetProgress();
		float	Current = Prog * sqrtf((mx*mx) + (my*my));
		if (m_Reverse)
		{
			return Index >=Current;
		} else
		{
			return Index <= Current;
		}
	}
};


class GameTransitionXWipe : public GameTransitionGrid
{
public:
	static GameTransition* Create(const std::string& Name)	{	return new GameTransitionXWipe(Name);	}
	GameTransitionXWipe(const std::string& Name) :	GameTransitionGrid(Name)
	{
	}
	virtual bool	IsGridOpen(int x, int y)
	{
		// top half scrolls in from the left, bottom half from the right
		int	Index = x;
		int	Band = (y*10) / GRID_Y;
		if (2*int(Band/2) == Band)		// even odd
		{
			Index = GRID_X - x;
		}
		float	Prog = m_Timer.GetProgress();
		float	Current = Prog * GRID_X;
		if (m_Reverse)
		{
			return Index >=Current;
		} else
		{
			return Index <= Current;
		}
	}
};

GameTransition::GameTransition(const std::string& Name):
	m_Name(Name),
	m_Timer(0.8f),
	m_TimeOut(0.1f),
	m_Reverse(false),
	m_OnComplete(0),
	m_pOwner(0),
	m_R(255), m_G(255), m_B(255)
{

}

GameTransition::~GameTransition()
{

}

void	GameTransition::Render()
{

}

void	GameTransition::Tick(float dt)
{
	if (!m_TimeOut.IsReady())
	{
		m_Timer.Tick(dt);
		if (m_Timer.IsReady())
		{
			m_TimeOut.Tick(dt);
			if (m_TimeOut.IsReady())
			{
				if (!m_OnDone.empty())
				{
					console::Console::Instance().Do(m_OnDone);
				}
				if (m_OnComplete)
				{
					m_OnComplete(this);
				}
			}

		}
	}
}


////////////////////////


TransitionManager::TransitionManager() :
	m_pFrom(0),
	m_pTo(0),
	m_R(0), m_G(0), m_B(0)
{
	//RegisterTransition("xwipe", GameTransitionXWipe::Create);
	//RegisterTransition("circle", GameTransitionCircle::Create);
	//RegisterTransition("grid", GameTransitionGrid::Create);
	//RegisterTransition("roll", GameTransitionRoll::Create);
	//RegisterTransition("iris", GameTransitionIris::Create);
	RegisterTransition("fade", GameTransitionFade::Create);
	//RegisterTransition("fadewhite", GameTransitionFadeWhite::Create);
}



TransitionManager::~TransitionManager()
{
	UTIL_SAFE_DELETE(m_pFrom);
	UTIL_SAFE_DELETE(m_pTo);
}

void		TransitionManager::GetTransition(const std::string& Name, GameTransition* &pFrom, GameTransition* &pTo)
{
	TransitionCreator* pSet = NULL;
	if (Name.empty())
	{
		// get a random one
		int	Index = rand() % m_Creators.GetElementC();
		pSet = m_Creators.GetElementByIndex(Index);
	} else
	{
		pSet = m_Creators.GetElement(Name);
	}
	if (!pSet)
	{
		_LOG(MSG_WARNING, "Couldn't find transition " << Name);
		return;
	}
	pFrom = pSet->pCB(Name);
	pTo   = pSet->pCB(Name);
	if (pTo)
	{
		pTo->SetReverse(true);
		pTo->SetOwner(this);
		pTo->SetColor(m_R, m_G, m_B);
	}
	if (pFrom)
	{
		pFrom->SetOwner(this);
		pFrom->SetColor(m_R, m_G, m_B);
	}
}
void		TransitionManager::StartTransition(const std::string& Name, const std::string& To, const std::string& Script, GameTransition::TransitionEvent OnComplete )
{
	// free anything that was already there
	UTIL_SAFE_DELETE(m_pFrom);
	UTIL_SAFE_DELETE(m_pTo);

	GetTransition(Name, m_pFrom, m_pTo);
	if (m_pFrom) 
	{
		m_pFrom->SetScript(Script);
		m_pFrom->SetFunctor(OnComplete);
	}
	m_NextState = To;
}

void		TransitionManager::DropTransition(const std::string& Name)
{
	m_Creators.RemoveElement(Name);
}

void		TransitionManager::RegisterTransition(const std::string& Name, TransitionCB pCreator)
{
	if (m_Creators.ElementExists(Name))
	{
		_LOG(MSG_WARNING, "already created a transition " << Name);
		return;
	}
	TransitionCreator* pSet = new TransitionCreator;
	pSet->Name = Name;
	pSet->pCB = pCreator;
	m_Creators.AddElement(Name, pSet);
}


void	TransitionManager::Tick(float dt)
{
	m_Sprite.Tick(dt);

	if (m_pFrom)
	{
		m_pFrom->Tick(dt);
		if (m_pFrom->IsOver())
		{
			UTIL_SAFE_DELETE(m_pFrom);
			if (!m_NextState.empty())
			{
				GameFlowManager::Instance().TransitionToState(m_NextState);
			}
		}
	} else
	if (m_pTo)
	{
		m_pTo->Tick(dt);
		if (m_pTo->IsOver())
		{
			UTIL_SAFE_DELETE(m_pTo);
		}
	} 
}


void	TransitionManager::SetTexture()
{
	if (m_Sprite.IsValid())
	{
		m_Sprite.SetTexture();	// if it's valid
	} else
	{
		GetGraphics()->SetTexture(0, NULL);
	}
}
void	TransitionManager::Render()
{
	if (!m_pFrom && !m_pTo) return;

	GetGraphics()->Start2D();
	if (m_pFrom)	m_pFrom->Render();
	else if (m_pTo) 	m_pTo->Render();
}


bool	TransitionManager::IsRunning()
{
	return (m_pFrom || m_pTo);
}


void	TransitionManager::SetupTransitionDg(ui::UIWidget* pWidget, 
								const std::string& ToState, 
								const std::string& Sound, 
								GameTransition::TransitionEvent		OnStart, 
								GameTransition::TransitionEvent	OnFinish)
{
	if (!pWidget)
	{
		return;
	}
	for (unsigned int i=0; i<m_Transitions.size(); i++)
	{
		if (m_Transitions[i]->pSourceWidget == pWidget)
		{
			// already exists!
			_LOG(MSG_ERROR, "There's already a transition state registered for " << pWidget->GetName());
			
			// provide overriding here instead?
			return;
		}
	}
	UITransition*	pTrans = new UITransition;
	pTrans->ToState = ToState;
	pTrans->pSourceWidget = pWidget;
	pTrans->PlaySound = Sound;
	pTrans->OnStart = OnStart;
	pTrans->OnFinish = OnFinish;
	m_Transitions.push_back(pTrans);
	pWidget->SetReleaseLMBEventDg(UI_DELEGATE(TransitionManager, OnStateTransitionClicked));
}

void DELEGATE_CALL	TransitionManager::OnStateTransitionClicked(ui::UIWidget::WIDGET_EVENT Event, ui::UIWidget* pWidget)
{
	if(m_pFrom && m_pTo)
		return;
	for (unsigned int i=0; i<m_Transitions.size(); i++)
	{
		if (m_Transitions[i]->pSourceWidget == pWidget)
		{
			// if there's an active function tied to OnStart, run it
			if (m_Transitions[i]->OnStart)
			{
				m_Transitions[i]->OnStart(NULL);	// no transition associated
			}
			// play sound?
			
			// spawn a transition
			// do we want a specific name?
			GameFlowManager::Instance().SetTransitionEffect(m_Transitions[i]->TransitionEffect, m_Transitions[i]->ToState, "", m_Transitions[i]->OnFinish);
			return;
		}
	}
}